<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>src/class.view.js - SceneGraph.js</title>
    <link rel="stylesheet" href="http://yui.yahooapis.com/3.9.1/build/cssgrids/cssgrids-min.css">
    <link rel="stylesheet" href="../assets/vendor/prettify/prettify-min.css">
    <link rel="stylesheet" href="../assets/css/main.css" id="site_styles">
    <link rel="shortcut icon" type="image/png" href="../assets/favicon.png">
    <script src="http://yui.yahooapis.com/combo?3.9.1/build/yui/yui-min.js"></script>
</head>
<body class="yui3-skin-sam">

<div id="doc">
    <div id="hd" class="yui3-g header">
        <div class="yui3-u-3-4">
            
                <h1><img src="../../logo/logoCGSG_256x57.png" title="SceneGraph.js"></h1>
            
        </div>
        <div class="yui3-u-1-4 version">
            <em>API Docs for: 2.2.0</em>
        </div>
    </div>
    <div id="bd" class="yui3-g">

        <div class="yui3-u-1-4">
            <div id="docs-sidebar" class="sidebar apidocs">
                <div id="api-list">
    <h2 class="off-left">APIs</h2>
    <div id="api-tabview" class="tabview">
        <ul class="tabs">
            <li><a href="#api-classes">Classes</a></li>
            <li><a href="#api-modules">Modules</a></li>
        </ul>

        <div id="api-tabview-filter">
            <input type="search" id="api-filter" placeholder="Type to filter APIs">
        </div>

        <div id="api-tabview-panel">
            <ul id="api-classes" class="apis classes">
            
                <li><a href="../classes/CGSG.html">CGSG</a></li>
            
                <li><a href="../classes/CGSGAccordion.html">CGSGAccordion</a></li>
            
                <li><a href="../classes/CGSGAnimationManager.html">CGSGAnimationManager</a></li>
            
                <li><a href="../classes/CGSGAnimationMethod.html">CGSGAnimationMethod</a></li>
            
                <li><a href="../classes/CGSGBindEntry.html">CGSGBindEntry</a></li>
            
                <li><a href="../classes/CGSGButtonMode.html">CGSGButtonMode</a></li>
            
                <li><a href="../classes/CGSGCollisionGhostOnDemandTester.html">CGSGCollisionGhostOnDemandTester</a></li>
            
                <li><a href="../classes/CGSGCollisionManager.html">CGSGCollisionManager</a></li>
            
                <li><a href="../classes/CGSGCollisionMethod.html">CGSGCollisionMethod</a></li>
            
                <li><a href="../classes/CGSGCollisionRegionTester.html">CGSGCollisionRegionTester</a></li>
            
                <li><a href="../classes/CGSGCollisionTesterFactory.html">CGSGCollisionTesterFactory</a></li>
            
                <li><a href="../classes/CGSGColor.html">CGSGColor</a></li>
            
                <li><a href="../classes/CGSGCSSManager.html">CGSGCSSManager</a></li>
            
                <li><a href="../classes/CGSGDimension.html">CGSGDimension</a></li>
            
                <li><a href="../classes/CGSGEvent.html">CGSGEvent</a></li>
            
                <li><a href="../classes/CGSGEventManager.html">CGSGEventManager</a></li>
            
                <li><a href="../classes/CGSGHandleBox.html">CGSGHandleBox</a></li>
            
                <li><a href="../classes/CGSGImgManager.html">CGSGImgManager</a></li>
            
                <li><a href="../classes/CGSGInterpolator.html">CGSGInterpolator</a></li>
            
                <li><a href="../classes/CGSGInterpolatorLinear.html">CGSGInterpolatorLinear</a></li>
            
                <li><a href="../classes/CGSGInterpolatorTCB.html">CGSGInterpolatorTCB</a></li>
            
                <li><a href="../classes/CGSGKeyFrame.html">CGSGKeyFrame</a></li>
            
                <li><a href="../classes/CGSGMap.html">CGSGMap</a></li>
            
                <li><a href="../classes/CGSGMask.html">CGSGMask</a></li>
            
                <li><a href="../classes/CGSGMaskCache.html">CGSGMaskCache</a></li>
            
                <li><a href="../classes/CGSGMaskClip.html">CGSGMaskClip</a></li>
            
                <li><a href="../classes/CGSGMath.html">CGSGMath</a></li>
            
                <li><a href="../classes/CGSGNode.html">CGSGNode</a></li>
            
                <li><a href="../classes/CGSGNodeButton.html">CGSGNodeButton</a></li>
            
                <li><a href="../classes/CGSGNodeCircle.html">CGSGNodeCircle</a></li>
            
                <li><a href="../classes/CGSGNodeColorPicker.html">CGSGNodeColorPicker</a></li>
            
                <li><a href="../classes/CGSGNodeCurveTCB.html">CGSGNodeCurveTCB</a></li>
            
                <li><a href="../classes/CGSGNodeDomElement.html">CGSGNodeDomElement</a></li>
            
                <li><a href="../classes/CGSGNodeEllipse.html">CGSGNodeEllipse</a></li>
            
                <li><a href="../classes/CGSGNodeImage.html">CGSGNodeImage</a></li>
            
                <li><a href="../classes/CGSGNodeLine.html">CGSGNodeLine</a></li>
            
                <li><a href="../classes/CGSGNodeSlider.html">CGSGNodeSlider</a></li>
            
                <li><a href="../classes/CGSGNodeSliderHandle.html">CGSGNodeSliderHandle</a></li>
            
                <li><a href="../classes/CGSGNodeSprite.html">CGSGNodeSprite</a></li>
            
                <li><a href="../classes/CGSGNodeSquare.html">CGSGNodeSquare</a></li>
            
                <li><a href="../classes/CGSGNodeTabMenu.html">CGSGNodeTabMenu</a></li>
            
                <li><a href="../classes/CGSGNodeText.html">CGSGNodeText</a></li>
            
                <li><a href="../classes/CGSGNodeWebview.html">CGSGNodeWebview</a></li>
            
                <li><a href="../classes/CGSGParticle.html">CGSGParticle</a></li>
            
                <li><a href="../classes/CGSGParticleEmitter.html">CGSGParticleEmitter</a></li>
            
                <li><a href="../classes/CGSGParticleSystem.html">CGSGParticleSystem</a></li>
            
                <li><a href="../classes/CGSGPickNodeMethod.html">CGSGPickNodeMethod</a></li>
            
                <li><a href="../classes/CGSGPosition.html">CGSGPosition</a></li>
            
                <li><a href="../classes/CGSGRegion.html">CGSGRegion</a></li>
            
                <li><a href="../classes/CGSGRotation.html">CGSGRotation</a></li>
            
                <li><a href="../classes/CGSGScale.html">CGSGScale</a></li>
            
                <li><a href="../classes/CGSGSceneGraph.html">CGSGSceneGraph</a></li>
            
                <li><a href="../classes/CGSGSection.html">CGSGSection</a></li>
            
                <li><a href="../classes/CGSGTimeline.html">CGSGTimeline</a></li>
            
                <li><a href="../classes/CGSGTraverser.html">CGSGTraverser</a></li>
            
                <li><a href="../classes/CGSGVector2D.html">CGSGVector2D</a></li>
            
                <li><a href="../classes/CGSGView.html">CGSGView</a></li>
            
                <li><a href="../classes/CGSGWEBVIEWMODE.html">CGSGWEBVIEWMODE</a></li>
            
                <li><a href="../classes/CGSGWrapMode.html">CGSGWrapMode</a></li>
            
                <li><a href="../classes/GLOBAL_CONSTANTS.html">GLOBAL_CONSTANTS</a></li>
            
                <li><a href="../classes/GLOBAL_METHODS.html">GLOBAL_METHODS</a></li>
            
                <li><a href="../classes/GLOBAL_PROPERTIES.html">GLOBAL_PROPERTIES</a></li>
            
                <li><a href="../classes/UTIL_ARRAY.html">UTIL_ARRAY</a></li>
            
                <li><a href="../classes/WUICCGSGNodeImageFactory.html">WUICCGSGNodeImageFactory</a></li>
            
            </ul>

            <ul id="api-modules" class="apis modules">
            
                <li><a href="../modules/Animation.html">Animation</a></li>
            
                <li><a href="../modules/Collision.html">Collision</a></li>
            
                <li><a href="../modules/Math.html">Math</a></li>
            
                <li><a href="../modules/Node.html">Node</a></li>
            
                <li><a href="../modules/ParticleSystem.html">ParticleSystem</a></li>
            
                <li><a href="../modules/Scene.html">Scene</a></li>
            
                <li><a href="../modules/Util.html">Util</a></li>
            
            </ul>
        </div>
    </div>
</div>

            </div>
        </div>
        <div class="yui3-u-3-4">
                <div id="api-options">
        Show:
        <label for="api-show-inherited">
            <input type="checkbox" id="api-show-inherited" checked>
            Inherited
        </label>

        <label for="api-show-protected">
            <input type="checkbox" id="api-show-protected">
            Protected
        </label>

        <label for="api-show-private">
            <input type="checkbox" id="api-show-private">
            Private
        </label>
        <label for="api-show-deprecated">
            <input type="checkbox" id="api-show-deprecated">
            Deprecated
        </label>

    </div>


            <div class="apidocs">
                <div id="docs-main">
                    <div class="content">
                        <h1 class="file-heading">File: src/class.view.js</h1>

<div class="file">
    <pre class="code prettyprint linenums">
/*
 * Copyright (c) 2014 Gwennael Buchet
 *
 * License/Terms of Use
 *
 * Permission is hereby granted, free of charge and for the term of intellectual property rights on the Software, to any
 * person obtaining a copy of this software and associated documentation files (the &quot;Software&quot;), to use, copy, modify
 * and propagate free of charge, anywhere in the world, all or part of the Software subject to the following mandatory conditions:
 *
 *   •    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 *  Any failure to comply with the above shall automatically terminate the license and be construed as a breach of these
 *  Terms of Use causing significant harm to Gwennael Buchet.
 *
 *  THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
 *  OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 *  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 *  Except as contained in this notice, the name of Gwennael Buchet shall not be used in advertising or otherwise to promote
 *  the use or other dealings in this Software without prior written authorization from Gwennael Buchet.
 *
 *  These Terms of Use are subject to French law.
 * */



/**
 * Provides requestAnimationFrame in a cross browser way.
 * @property cgsgGlobalRenderingTimer
 * @private
 * @type {Number}
 */
var cgsgGlobalRenderingTimer = null;
//var cgsgGlobalFramerate = CGSG_DEFAULT_FRAMERATE;
(function () {
    &quot;use strict&quot;;
    var lastTime = 0;
    var vendors = [&#x27;ms&#x27;, &#x27;moz&#x27;, &#x27;webkit&#x27;, &#x27;o&#x27;];
    for (var x = 0; x &lt; vendors.length &amp;&amp; !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + &#x27;RequestAnimationFrame&#x27;];
        window.cancelAnimationFrame =
        window[vendors[x] + &#x27;CancelAnimationFrame&#x27;] || window[vendors[x] + &#x27;CancelRequestAnimationFrame&#x27;];
    }

    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = function (callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 17 - (currTime - lastTime)); //1000/60 = 16.667
            cgsgGlobalRenderingTimer = window.setTimeout(function () {
                callback(currTime + timeToCall);
            }, timeToCall);
            lastTime = currTime + timeToCall;
            //return id;
        };
    }

    if (!window.cancelAnimationFrame) {
        window.cancelAnimationFrame = function (id) {
            clearTimeout(id);
        };
    }
}());

/**
 * Represent the scene of the application.
 * It encapsulates the scene graph itself and several methods to track mouse and touch events, ...
 *
 * @class CGSGView
 * @constructor
 * @module Scene
 * @main Scene
 * @extends {Object}
 * @param {HTMLElement} canvas a handler to the canvas HTML element
 * @type {CGSGView}
 * @author Gwennael Buchet (gwennael.buchet@gmail.com)
 */
var CGSGView = CGSGObject.extend(
    {
        initialize: function (canvas) {

            //detect the current explorer to apply correct parameters
            cgsgDetectCurrentExplorer();

            //for IE10 on Win8 : disable the default touch actions
            if (typeof canvas.style.msTouchAction !== &#x27;undefined&#x27;) {
                canvas.style.msTouchAction = &quot;none&quot;;
            }

            //noinspection JSUndeclaredVariable
            CGSG.canvas = canvas;
            //noinspection JSUndeclaredVariable
            CGSG.context = CGSG.canvas.getContext(&quot;2d&quot;);

            /**
             * Multiselection boolean.
             * @property allowMultiSelect
             * @default true
             * @type {Boolean}
             */
            this.allowMultiSelect = true;

            /**
             * Fill color for the drag selection selection rectangle
             * @property dragSelectFillColor
             * @type {String}
             */
            this.dragSelectFillColor = null;

            /**
             * Stroke color for the drag selection selection rectangle
             * @property dragSelectStrokeColor
             * @type {String}
             */
            this.dragSelectStrokeColor = null;

            /**
             * Stroke width for the drag selection selection rectangle
             * @property dragSelectStrokeWidth
             * @type {String}
             */
            this.dragSelectStrokeWidth = null;

            /**
             * Alpha value for the drag selection rectangle
             * @property dragSelectAlpha
             * @default 0.6
             * @type {Number}
             */
            this.dragSelectAlpha = CGSG_DEFAULT_DRAG_SELECT_ALPHA;

            //noinspection JSUndeclaredVariable
            this.dblBuffer = true;
            if (this.dblBuffer) {
                this._updateDblBuffer();
                CGSG.sceneGraph = new CGSGSceneGraph(CGSG.canvas, this._cacheCtx);
            }
            else {
                CGSG.sceneGraph = new CGSGSceneGraph(CGSG.canvas, CGSG.context);
            }

            /*
             * If true, framework will take care of multi-touch : NOT EFFECTIVE YET
             * @property multitouch
             * @default false
             * @type {Boolean}
             */
            //this.multitouch = false;

            ////// @private /////////
            /**
             * @property _isRunning
             * @type {Boolean}
             * @private
             */
            this._isRunning = false;
            // when set to true, the canvas will redraw everything
            // invalidateTransformation() just sets this to false right now
            // we want to call invalidateTransformation() whenever we make a change
            this._needRedraw = true;

            /**
             * @property _frameContainer Handler to the HTML Element displaying the FPS
             * @type {HTMLElement}
             * @private
             */
            this._frameContainer = null;

            /**
             * True if the [CTRL} key is being pressed
             * @property _keyDownedCtrl
             * @default false
             * @type {Boolean}
             * @private
             */
            this._keyDownedCtrl = false;

            /**
             * @property _timerDblTouch
             * @default null
             * @type {Number}
             * @private
             */
            this._timerDblTouch = null;

            /**
             * The delay between 2 touches to be considered as a dbl touch event.
             * To remove the double touch, just set it to 0
             * @property dblTouchDelay
             * @default CGSG_DEFAULT_DBLTOUCH_DELAY
             * @type {Number}
             */
            this.dblTouchDelay = CGSG_DEFAULT_DBLTOUCH_DELAY;

            /**
             * Current positions of the mouse or touch (Array of CGSGPosition)
             * @property _mousePos
             * @type {Array}
             * @private
             */
            this._mousePos = [];
            this._mouseOldPosition = [];
            this._dragStartPos = [];
            this._dragEndPos = [];
            this._isDrag = false;
            this._isResizeDrag = false;
            this._isDragSelect = false;
            this._resizingDirection = -1;
            this._isDblClick = false;
            this._mouseUpCount = 0; // counter used to identify dbl click action
            this._timeoutDblClick = null;
            this._isPressing = false;
            //this._frameRatio = 0;

            /**
             * @property _listCursors List of the names for the cursor when overring a handlebox
             * @type {Array}
             * @private
             */
            this._listCursors =
            [&#x27;nw-resize&#x27;, &#x27;n-resize&#x27;, &#x27;ne-resize&#x27;, &#x27;w-resize&#x27;, &#x27;e-resize&#x27;, &#x27;sw-resize&#x27;,
             &#x27;s-resize&#x27;, &#x27;se-resize&#x27;];
            this._offsetX = 0;
            this._offsetY = 0;
            /**
             * @property _selectedNode The current last selected node
             * @type {null}
             * @private
             */
            this._selectedNode = null;

            //Experimental : double-buffer for the temporary rendering
            /*this._dblCanvas = document.createElement(&#x27;canvas&#x27;);
             this._dblContext = null;*/

            ////// INITIALIZATION /////////

            //use an external variable to define the scope of the processes
            var scope = this;
            CGSG.canvas.onmouseout = function (e) {
                scope.onMouseOutHandler(e);
            };

            CGSG.canvas.onmousedown = function (e) {
                scope.onMouseDown(e);
            };
            CGSG.canvas.onmouseup = function (e) {
                scope.onMouseUp(e);
            };
            CGSG.canvas.ondblclick = function (e) {
                scope.onMouseDblClick(e);
            };
            CGSG.canvas.onmousemove = function (e) {
                scope.onMouseMove(e);
            };
            document.onkeydown = function (e) {
                scope.onKeyDownHandler(e);
            };
            document.onkeyup = function (e) {
                scope.onKeyUpHandler(e);
            };
            CGSG.canvas.addEventListener(&#x27;touchstart&#x27;, function (e) {
                scope.onTouchStart(e);
            }, false);
            CGSG.canvas.addEventListener(&#x27;touchmove&#x27;, function (e) {
                scope.onTouchMove(e);
            }, false);
            CGSG.canvas.addEventListener(&#x27;touchend&#x27;, function (e) {
                scope.onTouchEnd(e);
            }, false);

            CGSG.canvas.addEventListener(&#x27;MSPointerDown&#x27;, function (e) {
                scope.onTouchStart(e);
            }, false);
            CGSG.canvas.addEventListener(&quot;MSPointerMove&quot;, function (e) {
                scope.onTouchMove(e);
            }, false);
            CGSG.canvas.addEventListener(&#x27;MSPointerUp&#x27;, function (e) {
                scope.onTouchEnd(e);
            }, false);

            this._lastUpdate = new Date().getTime();

            this._nodeMouseOver = null;

            /**
             * Callback on click down on scene event.
             * @property onSceneClickStart
             * @default null
             * @type {Function}
             * @example
             *  this.onSceneClickStart = function (event) {
			 *      event.position; //Array of CGSGPosition
			 *      event.event; //Event
			 *  }
             */
            this.onSceneClickStart = null;
            /**
             * Callback on click up on scene event
             * @property onSceneClickEnd
             * @default null
             * @type {Function}
             * @example
             *  this.onSceneClickEnd = function (event) {
			 *      event.position; //Array of CGSGPosition
			 *      event.event; //Event
			 *  }
             */
            this.onSceneClickEnd = null;
            /**
             * Callback on double click start on scene event
             * @property onSceneDblClickStart
             * @default null
             * @type {Function}
             * @example
             *  this.onSceneDblClickStart = function (event) {
			 *      event.position; //Array of CGSGPosition
			 *      event.event; //Event
			 *  }
             */
            this.onSceneDblClickStart = null;
            /**
             * Callback on double click up on scene event
             * @property onSceneDblClickEnd
             * @default null
             * @type {Function}
             * @example
             *  this.onSceneDblClickEnd = function (event) {
			 *      event.position; //Array of CGSGPosition
			 *      event.event; //Event
			 *  }
             */
            this.onSceneDblClickEnd = null;
            /**
             * Callback on start rendering event
             * @property onRenderStart
             * @default null
             * @type {Function}
             * @example
             *  this.onSceneClickStart = function () {
			 *      //...
			 *  }
             */
            this.onRenderStart = null;
            /**
             * Callback on end rendering event
             * @property onRenderEnd
             * @default null
             * @type {Function}
             * @example
             *  this.onRenderEnd = function () {
			 *      //...
			 *  }
             */
            this.onRenderEnd = null;
            /**
             * Callback on frame average changed event.
             * @property onSceneAverageFtpChanged
             * @default null
             * @type {Function}
             * @example
             *  this.onSceneAverageFtsChanged = function (event) {
			 *      event.fps; // The average FPS  }
             */
            this.onSceneAverageFpsChanged = null;

            //initialize the current frame to 0
            //noinspection JSUndeclaredVariable
            CGSG.currentFrame = 0;
            this._fpss = null;

            this._r = {x: 0, y: 0};
            this._d = {dW: 0, dH: 0};

            //if CSS files was declared in &lt;head&gt; tag of index.html file, so we have to ask the framework
            // to load all components in cache
            this.invalidateTheme();
        },

        /**
         * @method _updateDblBuffer
         * @private
         */
        _updateDblBuffer: function () {
            if (!cgsgExist(this._cacheCanvas)) {
                this._cacheCanvas = document.createElement(&#x27;canvas&#x27;);
                this._cacheCtx = this._cacheCanvas.getContext(&#x27;2d&#x27;);
            }
            this._cacheCanvas.width = CGSG.canvas.width;
            this._cacheCanvas.height = CGSG.canvas.height;
            cgsgClearContext(this._cacheCtx);
        },

        /**
         * Change the dimension of the canvas.
         * Does not really change the dimension of the rendering canvas container,
         *  but is used by the different computations
         * @method setCanvasDimension
         * @param d{CGSGDimension} newDimension
         * */
        setCanvasDimension: function (d) {
            CGSG.canvas.width = d.width;
            CGSG.canvas.height = d.height;
            CGSG.sceneGraph.setCanvasDimension(d);
            this._updateDblBuffer();

            //Experimental
            /*this._dblCanvas.width = newDimension.x;
             this._dblCanvas.height = newDimension.y;
             this._dblContext = this._dblCanvas.getContext(&#x27;2d&#x27;);*/
        },

        /**
         * Remove the nodes selected in the scene graph
         * @method deleteSelected
         */
        deleteSelected: function () {
            if (CGSG.selectedNodes.length &gt; 0) {
                //for (var i = CGSG.selectedNodes.length - 1; i &gt;= 0; i--) {
                cgsgIterateReverse(CGSG.selectedNodes, (function (i, node) {
                    this._selectedNode = CGSG.selectedNodes[i];
                    CGSG.sceneGraph.removeNode(this._selectedNode, true);
                }).bind(this));
            }
        },

        /**
         * Deselect all nodes
         * @public
         * @method deselectAll
         * @param {Array} excludedArray CGSGNodes not to deselect
         */
        deselectAll: function (excludedArray) {
            this._isDrag = false;
            this._isResizeDrag = false;
            this._resizingDirection = -1;
            //CGSG.canvas.style.cursor = &#x27;auto&#x27;;
            CGSG.sceneGraph.deselectAll(excludedArray);
            this.invalidateTransformation();
        },

        /**
         * the main rendering loop
         * @protected
         * @method render
         */
        render: function () {
            if (this._isRunning &amp;&amp; this._needRedraw) {
                if (this.onRenderStart !== null) {
                    var evt = new CGSGEvent(this, null);
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_RENDER_START, evt);
                    //this.onRenderStart();
                }

                if (this.dblBuffer) {
                    cgsgClearContext(CGSG.context);
                    CGSG.context.drawImage(this._cacheCanvas, 0, 0);
                }

                CGSG.sceneGraph.render();

                //render the drag selection box directly onto the scene graph on top of everything else
                if (this._dragStartPos.length &gt; 0 &amp;&amp; this._dragEndPos.length &gt; 0) {

                    var p1 = this._dragStartPos[0];
                    var p2 = this._dragEndPos[0];

                    var dx = p2.x - p1.x, dy = p2.y - p1.y;

                    CGSG.sceneGraph.context.save();

                    CGSG.sceneGraph.context.scale(CGSG.displayRatio.x, CGSG.displayRatio.y);
                    CGSG.sceneGraph.context.strokeStyle = this.dragSelectStrokeColor;
                    CGSG.sceneGraph.context.fillStyle = this.dragSelectFillColor;
                    CGSG.sceneGraph.context.lineWidth = this.dragSelectStrokeWidth;
                    CGSG.sceneGraph.context.globalAlpha = this.dragSelectAlpha;
                    CGSG.sceneGraph.context.fillRect(p1.x, p1.y, dx, dy);
                    CGSG.sceneGraph.context.strokeRect(p1.x, p1.y, dx, dy);

                    CGSG.sceneGraph.context.restore();
                }

                if (this.onRenderEnd !== null) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_RENDER_END, new CGSGEvent(this, null));
                    //this.onRenderEnd();
                }

            }

            //if (!CGSG.sceneGraph.stillHaveAnimation()) {
            //	this._needRedraw = false;
            //}

            this._updateFramerate();
            this._updateFramerateContainer();
        },

        /**
         * Call this to start the update of the scene
         * @public
         * @method startPlaying
         */
        startPlaying: function () {
            //we want the callback of the requestAnimationFrame function to be this one.
            //however, the scope of &#x27;this&#x27; won&#x27;t be the same on the requestAnimationFrame function (scope = window)
            // and this one (scope = this). So we bind this function to this scope
            var bindStartPlaying = this.startPlaying.bind(this);
            window.requestAnimationFrame(bindStartPlaying);
            this._isRunning = true;
            this.render();
        },

        /**
         * Call this to stop the rendering (and so animation) update
         * @public
         * @method stopPlaying
         */
        stopPlaying: function () {
            window.cancelAnimationFrame(cgsgGlobalRenderingTimer);
            this._isRunning = false;
        },

        /**
         * Inform the SceneGraph that a new render is needed
         * @public
         * @method invalidateTransformation
         */
        invalidateTransformation: function () {
            this._needRedraw = true;
        },

        /**
         * Inform the SceneGraph that all nodes must be updated with the current theme
         * @method invalidateTheme
         */
        invalidateTheme: function () {
            CGSG.cssManager.invalidateCache();

            this.dragSelectFillColor = CGSG.cssManager.getAttr(&quot;cgsg-selectionBox&quot;, &quot;background-color&quot;);
            this.dragSelectStrokeColor = CGSG.cssManager.getAttr(&quot;cgsg-selectionBox&quot;, &quot;border-color&quot;);
            this.dragSelectStrokeWidth = CGSG.cssManager.getAttr(&quot;cgsg-selectionBox&quot;, &quot;border-width&quot;);
            this.dragSelectAlpha = CGSG.cssManager.getAttr(&quot;cgsg-selectionBox&quot;, &quot;opacity&quot;);

            //invalidateTransformation theme for all objects
            CGSG.sceneGraph.invalidateTheme();
        },

        /**
         * Update the current framerate
         * @method _updateFramerate
         * @private
         */
        _updateFramerate: function () {
            if (!cgsgExist(this._fpss)) {
                this._fpss = [];
                this.currentFps = 0;
            }

            var now = new Date().getTime();
            var delta = (now - this._lastUpdate);

            if (!isNaN(CGSG.maxFramerate)) {
                while ((1000.0 / delta) &gt; CGSG.maxFramerate) {
                    now = new Date().getTime();
                    delta = (now - this._lastUpdate);
                }
            }

            this._fpss[this.currentFps++] = 1000.0 / delta;

            if (this.currentFps === CGSG.framerateDelay) {
                this.currentFps = 0;
                CGSG.fps = this._fpss.average();
                if (this.onSceneAverageFpsChanged !== null) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_SCENE_AVERAGE_FPS_CHANGED,
                                               new CGSGEvent(this, {fps: CGSG.fps}));
                }
            }

            /*if (this._frameRatio === 0) {
             this._frameRatio = CGSG.fps;
             } else {
             this._frameRatio = ((this._frameRatio * (CGSG.currentFrame - 1)) + CGSG.fps) / CGSG.currentFrame;
             }*/

            this._lastUpdate = now;
        },

        /**
         * Update the innerHTML of the HTMLElement passed as parameter of the &quot;showFPS&quot; function
         * @method _updateFramerateContainer
         * @private
         */
        _updateFramerateContainer: function () {
            if (this._frameContainer !== null) {
                this._frameContainer.innerHTML = Math.round(CGSG.fps)/*.toString() + &quot; | ~&quot; + (this._frameRatio)*/;
            }
        },

        /**
         * @public
         * @method showFPS
         * @param {HTMLElement} elt an HTML element to receive the FPS. Can be null if you want to remove the framerate
         */
        showFPS: function (elt) {
            this._frameContainer = elt;
        },

        /**
         * Set the new value for the display ratio.
         * The display ratio is used to resize all the elements on the graph to be adapted to the screen,
         * depending on the reference screen size.
         * You can compute the ratio like this: x = canvas.width/reference.width ; y = canvas.height/reference.height
         * @public
         * @method setDisplayRatio
         * @param ratio {CGSGScale} a CGSGScale value
         */
        setDisplayRatio: function (ratio) {
            //noinspection JSUndeclaredVariable
            CGSG.displayRatio = ratio;
            CGSG.sceneGraph.initializeGhost(CGSG.canvas.width / CGSG.displayRatio.x,
                                            CGSG.canvas.height / CGSG.displayRatio.y);
        },

        /**
         * Detects when the mouse leaves the canvas.
         * @method onMouseOutHandler
         * @param e {MouseEvent} the event
         */
        onMouseOutHandler: function (e) {
            this._isPressing = false;
        },

        /**
         * click mouse Event handler function
         * @protected
         * @method onMouseDown
         * @param e {MouseEvent} event
         */
        onMouseDown: function (e) {
            this.onTouchStart(e);
        },

        /**
         * touch down Event handler function
         * @protected
         * @method onTouchStart
         * @param e {Event} event
         */
        onTouchStart: function (e) {
            this._isPressing = true;
            if (cgsgExist(this._mousePos)) {
                this._mouseOldPosition = this._mousePos.copy();
            }

            this._mousePos = cgsgGetCursorPositions(e, CGSG.canvas);
            this._selectedNode = CGSG.sceneGraph.pickNode(this._mousePos[0], null);
            if (cgsgExist(this._selectedNode)) {
                if (this._selectedNode.onClickStart) {
                    CGSG.eventManager.dispatch(this._selectedNode, cgsgEventTypes.ON_CLICK_START,
                                               new CGSGEvent(this,
                                                             {nativeEvent: e, position: this._mousePos}));
                }

                this._mouseOldPosition = this._mousePos.copy();
            }

            this._updateSelection(e);
        },

        /**
         * Updates the current selection according to the given event.
         *
         * @method _updateSelection
         * @param e {Event} the event
         * @private
         */
        _updateSelection: function (e) {
            //if a node is under the cursor, select it if it is (clickable || resizable || draggable)
            //s = selectable
            var s = cgsgExist(this._selectedNode) &amp;&amp;
                    (this._selectedNode.isClickable || this._selectedNode.isDraggable ||
                     this._selectedNode.isResizable);

            if (s) {
                if (this._selectedNode.isDraggable || this._selectedNode.isResizable) {
                    //if multiselection is activated
                    if (this.allowMultiSelect &amp;&amp; this._keyDownedCtrl) {
                        if (!this._selectedNode.isSelected) {
                            CGSG.sceneGraph.selectNode(this._selectedNode);
                        }
                        else {
                            CGSG.sceneGraph.deselectNode(this._selectedNode);
                        }
                    }
                    //no multiselection
                    else {
                        //if node not already selected
                        if (!this._selectedNode.isSelected) {
                            this.deselectAll(null);
                            CGSG.sceneGraph.selectNode(this._selectedNode);
                        }
                    }

                    this._isDrag = !this._detectResizeMode(this._mousePos[0]);

                    //ask for redraw
                    this.invalidateTransformation();
                }
            }
            //else if no nodes was clicked
            else {
                this.deselectAll(null);
            }

            // Check if we can start drag selection
            var canStartDragSelection = this._canStartDragSelection(e);

            // if the _canStartDragSelection has not been sub classed : apply this rule :
            // if no nodes were hit (that were clickable,resizeable or draggable) lets start a drag selection if we are allowed
            if (!cgsgExist(canStartDragSelection)) {
                canStartDragSelection = !s;
            }

            if (this.allowMultiSelect &amp;&amp; canStartDragSelection) {
                var p = cgsgGetCursorPositions(e, CGSG.canvas);
                this._isDragSelect = true;
                this._dragStartPos = p;
                this._dragEndPos = p;
                this.deselectAll(null);
            }
        },

        /**
         * This method indicates if, according to the current state of the scene, a drag selection could starts. Called
         * when a touchStart event triggered. Could be overridden to specify different behaviour.
         *
         * @method _canStartDragSelection
         * @protected
         * @param e {Event} the event
         * @return {Boolean} true if drag selection could starts, false otherwise
         */
        _canStartDragSelection: function (e) {
            // tells the caller to use default behaviour by returning nothing (undefined)
        },

        /**
         * Dispatch a &#x27;click&#x27; event and for any selected node which is clickable and and only if &#x27;this._isDblClick&#x27; == false.
         *
         * @method _dispatchClick
         * @param e {CGSGEvent} the event to dispatch
         * @private
         */
        _dispatchClick: function (e) {
            //execute the action bound with the click e
            if (cgsgExist(this._selectedNode) &amp;&amp; this._selectedNode.isClickable) {
                if (!this._isDblClick &amp;&amp; cgsgExist(this._selectedNode.onClick)) {
                    e.data.node = this._selectedNode;
                    e.data.positions = this._mousePos.copy();
                    CGSG.eventManager.dispatch(this._selectedNode, cgsgEventTypes.ON_CLICK, e);
                    //this._selectedNode.onClick({node: this._selectedNode, positions: this._mousePos.copy(), e: e});
                }
                //deselect all node except the new _selectedNode
                if (this._selectedNode.isDraggable === false &amp;&amp; this._selectedNode.isResizable === false) {
                    this.deselectAll([this._selectedNode]);
                }
            }
        },

        /**
         * Click on the scene
         *
         * @private
         * @method _clickOnScene
         * @param e {CGSGEvent} wrapper of MouseEvent or TouchEvent
         * @param {Boolean} pickNode
         */
        _clickOnScene: function (e, pickNode) {
            this._mousePos = cgsgGetCursorPositions(e.data.nativeEvent, CGSG.canvas);

            if (this.onSceneClickStart !== null) {
                CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_SCENE_CLICK_START, new CGSGEvent(this,
                                                                                                    {nativeEvent: e, positions: this._mousePos}));
                //this.onSceneClickStart({positions: this._mousePos.copy(), e: e});
            }

            //try to pick up the nodes under the cursor
            if (pickNode) {
                this._selectedNode = CGSG.sceneGraph.pickNode(this._mousePos[0], function (node) {
                    return (node.isTraversable === true &amp;&amp; (node.isClickable === true ||
                        node.isDraggable === true || node.isResizable === true));
                });
            }

            //this._updateSelection(e);
            this._dispatchClick(e);

            if (this.onSceneClickEnd !== null) {
                CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_SCENE_CLICK_END, new CGSGEvent(this,
                                                                                                  {nativeEvent: e, positions: this._mousePos}));
                //this.onSceneClickEnd({positions: this._mousePos.copy(), e: e});
            }

            //this._mouseOldPosition = this._mousePos.copy();
        },

        /**
         * mouse move Event handler function
         * @protected
         * @method onMouseMove
         * @param e {MouseEvent} event
         */
        onMouseMove: function (e) {
            this._moveOnScene(e);
        },

        /**
         * touch move Event handler function
         * @protected
         * @method onTouchMove
         * @param {Event} e
         */
        onTouchMove: function (e) {
            if (e.preventManipulation) {
                e.preventManipulation();
            }
            e.preventDefault();
            e.stopPropagation();
            this._moveOnScene(e);
        },

        /**
         * @private
         * @method _moveOnScene
         * @param e {Event} MouseEvent or TouchEvent
         */
        _moveOnScene: function (e) {
            var i, offX, offY, evt, mp, mop;
            this._mousePos = cgsgGetCursorPositions(e, CGSG.canvas);
            var selN = this._selectedNode;
            this._selectedNode = null;

            if (this._isPressing &amp;&amp; this._isDrag) {
                if (CGSG.selectedNodes.length &gt; 0) {
                    mp = this._mousePos[0];
                    mop = this._mouseOldPosition[0];
                    this._offsetX = mp.x - mop.x;
                    this._offsetY = mp.y - mop.y;
                    for (i = CGSG.selectedNodes.length - 1; i &gt;= 0; i--) {
                        this._selectedNode = CGSG.selectedNodes[i];
                        if (this._selectedNode !== null &amp;&amp; this._selectedNode.isDraggable) {
                            this._selectedNode.isMoving = true;
                            //TODO : appliquer aussi l&#x27;opposée de la rotation
                            offX = this._offsetX /
                                   (this._selectedNode._absSca.x / this._selectedNode.scale.x);
                            offY = this._offsetY /
                                   (this._selectedNode._absSca.y / this._selectedNode.scale.y);

                            //check for the region constraint

                            if (this._canMove(this._selectedNode, offX, offY, 0, 0)) {
                                this._selectedNode.translateWith(offX, offY);
                                if (this._selectedNode.onDrag !== null) {
                                    evt = new CGSGEvent(this,
                                                            {node: this._selectedNode, positions: this._mousePos.copy(), nativeEvent: e});
                                    CGSG.eventManager.dispatch(this._selectedNode, cgsgEventTypes.ON_DRAG, evt);
                                    //this._selectedNode.onDrag({node: this._selectedNode, positions: this._mousePos.copy(), e: e});
                                }
                            }
                        }
                    }

                    this._mouseOldPosition = this._mousePos.copy();

                    // something is changing position so we better invalidateTransformation the canvas!
                    this.invalidateTransformation();
                }
            }
            else if (this._isPressing &amp;&amp; this._isResizeDrag) {
                if (CGSG.selectedNodes.length &gt; 0) {
                    mp = this._mousePos[0];
                    mop = this._mouseOldPosition[0];
                    this._offsetX = mp.x - mop.x;
                    this._offsetY = mp.y - mop.y;

                    //for (i = CGSG.selectedNodes.length - 1; i &gt;= 0; i--) {
                    cgsgIterateReverse(CGSG.selectedNodes, (function (i, node) {
                        this._selectedNode = node;//CGSG.selectedNodes[i];

                        if (this._selectedNode.isResizable) {
                            this._selectedNode.isResizing = true;
                            //TODO : appliquer aussi l&#x27;opposée de la rotation
                            offX = this._offsetX / this._selectedNode._absSca.x;
                            offY = this._offsetY / this._selectedNode._absSca.y;

                            var delta = Math.max(offX, offY);
                            if (delta === 0) {
                                delta = Math.min(offX, offY);
                            }
                            var realDimX = this._selectedNode.getWidth() * //this._selectedNode.dimension.width *
                                           this._selectedNode._absSca.x;
                            var realDimY = this._selectedNode.getHeight() * //this._selectedNode.dimension.height *
                                           this._selectedNode._absSca.y;

                            // 0  1  2
                            // 3     4
                            // 5  6  7
                            switch (this._resizingDirection) {
                                case 0:
                                    if (this._selectedNode.isProportionalResize) {
                                        this._d = this._getDeltaOnMove(delta, offX, offY, realDimX,
                                                                       realDimY,
                                                                       -1, -1);
                                        this._r.x = this._d.dW;
                                        this._r.y = this._d.dH;
                                        this._d.dW = -this._d.dW;
                                        this._d.dH = -this._d.dH;
                                    }
                                    else {
                                        this._d.dW = offX * this._selectedNode.scale.x;
                                        this._d.dH = offY * this._selectedNode.scale.y;
                                        this._r.x = -offX;
                                        this._r.y = -offY;
                                    }
                                    break;
                                case 1:
                                    this._d.dW = 0;
                                    this._d.dH = offY * this._selectedNode.scale.y;
                                    this._r.x = 0;
                                    this._r.y = -offY;
                                    break;
                                case 2:
                                    if (this._selectedNode.isProportionalResize) {
                                        this._d = this._getDeltaOnMove(delta, offX, offY, realDimX,
                                                                       realDimY,
                                                                       1, -1);

                                        this._r.x = this._d.dW;
                                        this._r.y = this._d.dH;
                                        this._d.dW = 0;
                                        this._d.dH = -this._d.dH;
                                    }
                                    else {

                                        this._d.dW = 0;
                                        this._d.dH = offY * this._selectedNode.scale.y;
                                        this._r.x = offX;
                                        this._r.y = -offY;
                                    }
                                    break;
                                case 3:

                                    this._d.dW = offX * this._selectedNode.scale.x;
                                    this._d.dH = 0;
                                    this._r.x = -offX;
                                    this._r.y = 0;
                                    break;
                                case 4:
                                    this._d.dW = 0;
                                    this._d.dH = 0;
                                    this._r.x = offX;
                                    this._r.y = 0;
                                    break;
                                case 5:
                                    if (this._selectedNode.isProportionalResize) {
                                        this._d = this._getDeltaOnMove(delta, offX, offY, realDimX,
                                                                       realDimY,
                                                                       1, -1);
                                        this._r.x = -this._d.dW;
                                        this._r.y = -this._d.dH;
                                        this._d.dH = 0;
                                    }
                                    else {

                                        this._d.dW = offX * this._selectedNode.scale.x;
                                        this._d.dH = 0;
                                        this._r.x = -offX;
                                        this._r.y = offY;
                                    }
                                    break;
                                case 6:
                                    this._d.dW = 0;
                                    this._d.dH = 0;
                                    this._r.x = 0;
                                    this._r.y = offY;
                                    break;
                                case 7:
                                    if (this._selectedNode.isProportionalResize) {
                                        this._d = this._getDeltaOnMove(delta, offX, offY, realDimX,
                                                                       realDimY,
                                                                       1, 1);

                                        this._r.x = this._d.dW;
                                        this._r.y = this._d.dH;
                                        this._d.dW = 0;
                                        this._d.dH = 0;
                                    }
                                    else {
                                        this._d.dW = 0;
                                        this._d.dH = 0;
                                        this._r.x = offX;
                                        this._r.y = offY;
                                    }
                                    break;
                            }

                            if (this._canMove(this._selectedNode, this._d.dW, this._d.dH, this._r.x, this._r.y)) {
                                this._selectedNode.translateWith(this._d.dW, this._d.dH, false);
                                this._selectedNode.resizeWith(this._r.x, this._r.y, false);

                                this._selectedNode.computeAbsoluteMatrix(false);
                                if (this._selectedNode.onResize !== null) {
                                    var evt = new CGSGEvent(this,
                                                            {node: this._selectedNode, positions: this._mousePos.copy(), e: e});
                                    CGSG.eventManager.dispatch(this._selectedNode, cgsgEventTypes.ON_RESIZE, evt);
                                }
                            }
                        }

                    }).bind(this));
                }
                this._mouseOldPosition = this._mousePos.copy();

                this.invalidateTransformation();
            }
            // if there&#x27;s a selection, see if we grabbed one of the resize handles
            else if (CGSG.selectedNodes.length &gt; 0/* &amp;&amp; this._isResizeDrag == false*/) {
                if (this._detectResizeMode(this._mousePos[0])) {
                    return;
                }
                else {
                    // not over a selection box, return to normal
                    //this._isResizeDrag = false;
                    this._resizingDirection = -1;
                    CGSG.canvas.style.cursor = &#x27;auto&#x27;;

                    //ask for redraw
                    this.invalidateTransformation();
                }
            }
            //if we are drag selecting
            else if (this._isDragSelect) {
                this._dragEndPos = this._mousePos.copy();

                //ask to redraw for the selection box
                this.invalidateTransformation();
            }

            //mouse over a node ?
            if (!this._isDrag &amp;&amp; !this._isResizeDrag) {
                var n = null;
                //first test the mouse over the current _nodeMouseOver. If it&#x27;s ok, no need to traverse other
                if (cgsgExist(this._nodeMouseOver)) {
                    n = this._nodeMouseOver.pickNode(this._mousePos[0], null, CGSG.ghostContext, false, null);

                    if (n === null) {
                        this._nodeMouseOver.isMouseOver = false;
                        if (cgsgExist(this._nodeMouseOver.onMouseOut)) {
                            evt = new CGSGEvent(this,
                                                    {node: this._nodeMouseOver, positions: this._mousePos.copy(), e: e});
                            CGSG.eventManager.dispatch(this._nodeMouseOver, cgsgEventTypes.ON_MOUSE_OUT, evt);
                            //this._nodeMouseOver.onMouseOut({node: this._nodeMouseOver, positions: this._mousePos.copy(), e: e});
                        }
                        this._nodeMouseOver = null;
                    }
                    else if (n === this._nodeMouseOver) {
                        if (cgsgExist(this._nodeMouseOver.onMouseOver)) {
                            evt = new CGSGEvent(this,
                                                    {node: this._nodeMouseOver, positions: this._mousePos.copy(), e: e});
                            CGSG.eventManager.dispatch(this._nodeMouseOver, cgsgEventTypes.ON_MOUSE_OVER, evt);
                            //this._nodeMouseOver.onMouseOver({node: this._nodeMouseOver, positions: this._mousePos.copy(), e: e});
                        }
                    }
                }

                //if the previous node under the mouse is no more under the mouse, test the other nodes
                if (n === null) {
                    if ((n = CGSG.sceneGraph.pickNode(this._mousePos[0], function (node) {
                        return (node.onMouseEnter !== null || node.onMouseOver !== null);
                    })) !== null) {
                        n.isMouseOver = true;
                        this._nodeMouseOver = n;
                        this._nodeMouseOver.isMouseOver = true;
                        if (cgsgExist(this._nodeMouseOver.onMouseEnter)) {
                            evt = new CGSGEvent(this,
                                                    {node: this._nodeMouseOver, positions: this._mousePos.copy(), e: e});
                            CGSG.eventManager.dispatch(this._nodeMouseOver, cgsgEventTypes.ON_MOUSE_ENTER, evt);
                            //this._nodeMouseOver.onMouseEnter({node: this._nodeMouseOver, positions: this._mousePos.copy(), e: e})
                        }
                    }
                }
            }

            this._selectedNode = selN;
        },

        _canMove: function (node, dX, dY, dW, dH) {
            var rgc;
            if (node.nodeConstraint !== null) {
                rgc = node.nodeConstraint.getAbsoluteRegion();
            }
            else {
                rgc = node.regionConstraint;
            }
            if (rgc !== null) {
                var reg = node.getAbsoluteRegion();
                reg.position.x += dX;
                reg.position.y += dY;
                reg.dimension.width += dW;
                reg.dimension.height += dH;
                if (!cgsgRegionIsInRegion(reg, rgc, 0)) {
                    return false;
                }
            }
            return true;
        },

        /**
         * Detects if the mouse if over the handle box of a selected node.
         *
         * @method _detectResizeMode
         * @param pos {CGSGPosition} the cursor position
         * @return {Boolean} true if we resize, false otherwise
         * @private
         */
        _detectResizeMode: function (pos) {
            var sel = this._selectedNode;

            cgsgIterateReverse(CGSG.selectedNodes, (function (i, node) {
                this._selectedNode = node;
                if (this._selectedNode.isResizable) {
                    for (var h = 0; h &lt; 8; h++) {
                        if (node.isProportionalResizeOnly &amp;&amp; (h === 1 || h === 3 || h === 4 || h === 6)) {
                            continue;
                        }
                        var selectionHandle = this._selectedNode.handles[h];
                        this._isResizeDrag = selectionHandle.checkIfSelected(pos, CGSG.resizeHandleThreshold);

                        // resize handles will always be rectangles
                        if (this._isResizeDrag) {
                            // we found one!
                            this._resizingDirection = h;

                            //draw the correct cursor
                            CGSG.canvas.style.cursor = this._listCursors[h];

                            //if the mouse cursor is over a handle box (ie: a resize marker)
                            // if (this._resizingDirection !== -1) {
                            //     this._isResizeDrag = true;
                            //}

                            return false;
                        }
                    }
                }
            }).bind(this));

            this._selectedNode = sel;
            return this._isResizeDrag;
        },

        /**
         * @method _getDeltaOnMove
         * @param delta {Number}
         * @param offX {Number} nodeOffsetX
         * @param offY {Number} nodeOffsetY
         * @param w {Number}
         * @param h {Number}
         * @param signeX {Number}
         * @param signeY {Number}
         * @return {Object}
         * @private
         */
        _getDeltaOnMove: function (delta, offX, offY, w, h, signeX, signeY) {
            var dW = offX, dH = offY;
            var r = 1.0;
            if (delta === offX) {
                r = (w + signeX * delta) / w;
                dW = signeX * delta;
                dH = (r - 1.0) * h;
            }
            else {
                r = (h + signeY * delta) / h;
                dH = signeY * delta;
                dW = (r - 1.0) * w;
            }

            return {dW: dW, dH: dH};
        },

        /**
         * mouse up Event handler function
         * @protected
         * @method onMouseUp
         * @param {MouseEvent} e
         */
        onMouseUp: function (e) {
            this.onTouchEnd(e);
        },

        /**
         * touch up Event handler function
         * @protected
         * @method onTouchEnd
         * @param {Event} e
         */
        onTouchEnd: function (e) {
            if (e.preventManipulation) {
                e.preventManipulation();
            }
            e.preventDefault();
            e.stopPropagation();

            this._isPressing = false;
            this._mouseUpCount++;

            //if the touch was over a node with the onDblClick method defined, check whether it&#x27;s a dbl touch or not
            if (cgsgExist(this._selectedNode) &amp;&amp; cgsgExist(this._selectedNode.onDblClick)) {

                //if the timer exists, then it&#x27;s a dbl touch
                if (this._mouseUpCount &gt; 1) {
                    clearTimeout(this._timeoutDblClick);
                    this._upAndDblClick(e);
                }
                else {
                    this._timeoutDblClick = setTimeout((function () {
                        this._upAndClick(e);
                    }).bind(this), this.dblTouchDelay);
                }
            }
            else {
                // not a touch on a node with the onDblClick e defined,
                // so it&#x27;s a single touch, just call the _clickOnScene method usually
                this._upAndClick(e);
            }
        },

        /**
         * Creates the custom event by calling _upOnScene and then call _clickOnScene.
         *
         * @method _upAndClick
         * @param {Event} e the event
         * @private
         */
        _upAndClick: function (e) {
            this._mouseUpCount = 0;
            var ed = this._upOnScene(e);
            ed.nativeEvent = e;
            this._clickOnScene(new CGSGEvent(this, ed), false);
        },

        /**
         * Creates the custom event by calling _upOnScene and then call _dblClickOnScene.
         *
         * @method _upAndDblClick
         * @param {Event} e the event
         * @private
         */
        _upAndDblClick: function (e) {
            this._mouseUpCount = 0;
            var eventData = this._upOnScene(e);
            eventData.nativeEvent = e;
            this._dblClickOnScene(new CGSGEvent(this, eventData), false);
        },

        /**
         * @method _upOnScene
         * @param {Event} e MouseEvent or TouchEvent
         * @return {Object} a structure indicating is the node has been moved or resize
         * @private
         */
        _upOnScene: function (e) {
            var sel = this._selectedNode;
            var i = 0;
            var retval = {};
            retval.nativeEvent = e;

            var exist = cgsgExist(sel);
            retval.hasMoved = exist &amp;&amp; sel.isMoving;
            retval.hasResize = exist &amp;&amp; sel.isResizing;

            //if current action was to drag nodes
            if (this._isDrag) {
                cgsgIterateReverse(CGSG.selectedNodes, (function (i, node) {
                    //for (i = CGSG.selectedNodes.length - 1; i &gt;= 0; i--) {
                    //    this._selectedNode = CGSG.selectedNodes[i];
                    this._selectedNode = node;

                    if (this._selectedNode.isMoving) {
                        this._selectedNode.isMoving = false;
                        this._selectedNode.computeAbsoluteMatrix(true);

                        if (this._selectedNode.onDragEnd !== null) {
                            var evt = new CGSGEvent(this,
                                                    {node: this._selectedNode, positions: this._mousePos.copy(), nativeEvent: e});
                            CGSG.eventManager.dispatch(this._selectedNode, cgsgEventTypes.ON_DRAG_END, evt);
                            //    this._selectedNode.onDragEnd({node: this._selectedNode, positions: this._mousePos.copy(), e: e});
                        }
                    }
                    //}
                }).bind(this));

                this._isDrag = false;
            }

            //else if current action was to resize nodes
            else if (this._isResizeDrag) {
                cgsgIterateReverse(CGSG.selectedNodes, (function (i, node) {
                    //for (i = CGSG.selectedNodes.length - 1; i &gt;= 0; i--) {
                    //this._selectedNode = CGSG.selectedNodes[i];
                    this._selectedNode = node;

                    if (this._selectedNode.isResizing) {
                        this._selectedNode.isResizing = false;
                        this._selectedNode.computeAbsoluteMatrix(true);

                        if (this._selectedNode.onResizeEnd !== null) {
                            var evt = new CGSGEvent(this,
                                                    {node: this._selectedNode, positions: this._mousePos.copy(), nativeEvent: e});
                            CGSG.eventManager.dispatch(this._selectedNode, cgsgEventTypes.ON_RESIZE_END, evt);
                            //this._selectedNode.onResizeEnd({node: this._selectedNode, positions: this._mousePos.copy(), e: e});
                        }
                    }
                    //}
                }).bind(this));

                this._isResizeDrag = false;
            }
            //else if this is a drag select
            else if (this._isDragSelect) {
                this._isDragSelect = false;
                this._doDragSelect();
                this._dragStartPos = [];
                this._dragEndPos = [];

                //request a re-render for the drag select rect to be killed with
                this.invalidateTransformation();
            }

            //else if just up the mice of nodes
            else {
                this._selectedNode = CGSG.selectedNodes[CGSG.selectedNodes.length - 1];
                if (cgsgExist(this._selectedNode) &amp;&amp; this._selectedNode.onMouseUp !== null) {
                    var evt = new CGSGEvent(this,
                                            {node: this._selectedNode, positions: this._mousePos.copy(), e: e});
                    CGSG.eventManager.dispatch(this._selectedNode, cgsgEventTypes.ON_MOUSE_UP, evt);
                    //this._selectedNode.onMouseUp({node: this._selectedNode, positions: this._mousePos.copy(), e: e});
                }
            }

            this._resizingDirection = -1;
            this._selectedNode = sel;
            return retval;
        },

        /**
         * Select the nodes under the drag select rectangle
         * @protected
         * @method _doDragSelect
         */
        _doDragSelect  : function () {

            var p1 = this._dragStartPos[0];
            var p2 = this._dragEndPos[0];

            var dx = p2.x - p1.x, dy = p2.y - p1.y;

            if (dx &lt; 0) {
                dx = Math.abs(dx);
                p1.x -= dx;
            }

            if (dy &lt; 0) {
                dy = Math.abs(dy);
                p1.y -= dy;
            }

            var region = new CGSGRegion(p1.x, p1.y, dx, dy);
            var newSelections = CGSG.sceneGraph.pickNodes(region, function (node) {
                return (node.isTraversable === true &amp;&amp; (/*node.isClickable === true ||*/ node.isDraggable === true ||
                    node.isResizable === true));
            });

            for (var i = 0, len = newSelections.length; i &lt; len; ++i) {
                CGSG.sceneGraph.selectNode(newSelections[i]);
            }
        },
        /**
         * mouse double click Event handler function
         * @protected
         * @method onMouseDblClick
         * @param {MouseEvent} e
         */
        onMouseDblClick: function (e) {
            //this._dblClickOnScene(event);
            e.preventDefault();
            e.stopPropagation();
        },

        /**
         * @protected
         * @method _dblClickOnScene
         * @param {CGSGEvent} e wrapping the native event
         * @param {Boolean} mustPickNode
         * @return {CGSGNode} the node that was double-clicked
         * @private
         */
        _dblClickOnScene: function (e, mustPickNode) {
            //this._updateSelection(e);

            if (this.onSceneDblClickStart !== null) {
                CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_SCENE_DBL_CLICK_START, e);
                //this.onSceneDblClickStart(e);
            }

            if (mustPickNode) {
                //this._mousePos = cgsgGetCursorPositions(e, CGSG.canvas);
                this._selectedNode = CGSG.sceneGraph.pickNode(this._mousePos[0], function (node) {
                    return true;
                });
            }

            if (cgsgExist(this._selectedNode) &amp;&amp; this._selectedNode.onDblClick !== null) {
                e.data.node = this._selectedNode;
                e.data.positions = this._mousePos.copy();
                CGSG.eventManager.dispatch(this._selectedNode, cgsgEventTypes.ON_DBL_CLICK, e);
                //this._selectedNode.onDblClick({node: this._selectedNode, positions: this._mousePos.copy(), e: e});
            }
            else if (this.onSceneDblClickEnd !== null) {
                CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_SCENE_DBL_CLICK_END, e);
                //this.onSceneDblClickEnd({positions: this._mousePos.copy(), e: e});
            }
            return this._selectedNode;
        },

        /**
         * @method onKeyDownHandler
         * @protected
         * @param {KeyboardEvent} e
         * @return {Number}
         */
        onKeyDownHandler: function (e) {
            var keynum = (window.e) ? e.keyCode : e.which;

            switch (keynum) {
                case 17:
                    this._keyDownedCtrl = true;
                    break;
            }

            return keynum;
        },

        /**
         * @method onKeyUpHandler
         * @protected
         * @param {KeyboardEvent} ee
         * @return {Number}
         */
        onKeyUpHandler: function (e) {
            var keynum = (window.e) ? e.keyCode : e.which;

            switch (keynum) {
                case 17:
                    this._keyDownedCtrl = false;
                    break;
            }

            return keynum;
        }
    }
);

    </pre>
</div>

                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<script src="../assets/vendor/prettify/prettify-min.js"></script>
<script>prettyPrint();</script>
<script src="../assets/js/yui-prettify.js"></script>
<script src="../assets/../api.js"></script>
<script src="../assets/js/api-filter.js"></script>
<script src="../assets/js/api-list.js"></script>
<script src="../assets/js/api-search.js"></script>
<script src="../assets/js/apidocs.js"></script>
</body>
</html>
